﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Bluefly
{

    public class PiecewiseLinearFunction
    {
        private List<Point2d> points;
        private double posInfValue;
        private double negInfValue;


        /**
         * Creates a piecewise linear function from an ArrayList of
         * Point2d objects. Throws a runtime exception if the
         * arraylist contains other than Point2d objects. Adds points to the list of
         * points using the addNewPoint(Point2d) method.
         */

        public PiecewiseLinearFunction(List<Point2d> point2ds) {
        points = new List<Point2d>();
        posInfValue = Double.NaN;
        negInfValue = Double.NaN;
        for (int i = 0; i < point2ds.Count(); i++) {
            Point2d point;
            try {
                point = point2ds[i];
            } catch (Exception e) {

                //System.out.println("Must use only javax.Vecmath.Point2d objects");
                //e.printStackTrace();
                throw new Exception("Must use only Point2d objects", e);
            }
            this.addNewPoint(point);
        }
        if (points.Count() < 1) {
            //System.out.println("Must have at least one valid point");

            throw new Exception("Must have at least one valid point");
        }
    }

        /**
         * Creates a piecewiseLinearFunction with only one point
         * This would normally be added to later with the addNewPoint method
         */

        public PiecewiseLinearFunction(Point2d point) {
        points = new List<Point2d>();
        posInfValue = Double.NaN;
        negInfValue = Double.NaN;
        this.addNewPoint(point);
        if (points.Count() < 1) {
            //System.out.println("Must have at least one valid point");
            throw new Exception();
        }
    }

        /**
         * Point2d objects with a x value of Double.NaN are ignored.
         * Those with an x value equal to Double.PositiveInfinity or
         * Double.NegativeInfinity are used to set the value of
         * the posInfValue and negInfValue fields respectively.
         * If the values are not specified then posInfValue and negInfValue
         * are set to Double.NaN.
         * If the y value == Double.NaN, PositiveInfinity or Negative Infinity
         * the point will not be added.
         */

        public void addNewPoint(Point2d point)
        {
            if (Double.IsNaN(point.x) || Double.IsNaN( point.y) ||
                    point.y == Double.PositiveInfinity || point.y == Double.NegativeInfinity)
            {
                return;
            }
            else if (point.x == Double.PositiveInfinity)
            {
                posInfValue = point.y;
            }
            else if (point.x == Double.NegativeInfinity)
            {
                negInfValue = point.y;
            }
            else if (points.Count() == 0)
            {
                points.Add(point);
                return;
            }
            else if (point.x > (points[points.Count() - 1]).x)
            {
                points.Add(point);
            }
            else
            {
                for (int i = 0; i < points.Count(); i++)
                {
                    if (points[i].x > point.x)
                    {
                        points.Insert(i, point);
                        return;
                    }
                }
            }
        }

        /**
         * Calls addNewPoint(Point2d) provided point is not null and is of length two. Returns true if successful
         */

        public bool addNewPoint(double[] point)
        {
            if (point != null && point.Length == 2)
            {

                this.addNewPoint(new Point2d(point[0], point[1]));

                return true;


            }

            return false;
        }

        /**
         * Returns the linear interpolation between two points. If s is
         * less than the first point, or the greater than the last point then that
         * that points y value is returned. If x  == pos or neg Infinity then the value
         * of those fields are returned.
         */

        public double getValue(double x)
        {
            if (x == Double.PositiveInfinity)
            {
                return posInfValue;
            }
            else if (x == Double.NegativeInfinity)
            {
                return negInfValue;
            }
            else if (points.Count() == 1)
            {
                return (points[0]).y;
            }
            else
            {
                Point2d point;
                Point2d lastPoint = points[0];
                if (x <= lastPoint.x)
                {
                    return lastPoint.y;
                }
                for (int i = 1; i < points.Count(); i++)
                {
                    point = points[i];
                    if (x <= point.x)
                    {
                        double ratio = (x - lastPoint.x) / (point.x - lastPoint.x);
                        return lastPoint.y + ratio * (point.y - lastPoint.y);
                    }
                    lastPoint = point;
                }
                return lastPoint.y;
            }
        }

        public String toString()
        {
            String returnValue = String.Empty;
            if (!Double.IsNaN(negInfValue))
            {
                returnValue = returnValue + ("(negInfinity, " + negInfValue + ")\n");
            }
            for (int i = 0; i < points.Count(); i++)
            {
                Point2d point = points[i];
                returnValue = returnValue + (point + "\n");
            }
            if (!Double.IsNaN(posInfValue))
            {
                returnValue = returnValue + ("(posInfinity, " + posInfValue + ")\n");
            }
            return returnValue;
        }


    }



}
